/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.admin.manager.impl;

import vip.dexian.admin.assembler.AdminToPrincipalAssembler;
import vip.dexian.admin.dao.AdminDao;
import vip.dexian.admin.dao.RoleDao;
import vip.dexian.admin.dto.AdminPrincipal;
import vip.dexian.admin.dto.AdminRoleDTO;
import vip.dexian.admin.entity.Admin;
import vip.dexian.admin.manager.AdminManager;
import vip.dexian.common.utils.IpToLongUtils;
import vip.dexian.common.utils.LocalDateTimeUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;

/**
 * 管理员manager， 主要解决循环依赖问题
 *
 * @author 挺好的 2023年06月06日 17:59
 */
@Service ("adminManagerImpl")
@Slf4j
public class AdminManagerImpl implements AdminManager {

    /**
     * 管理员Dao
     */
    @Resource (name = "adminDaoImpl")
    private AdminDao adminDao;

    /**
     * 角色dao
     */
    @Resource (name = "roleDaoImpl")
    private RoleDao roleDao;

    /**
     * admin -> 凭证映射
     */
    @Resource (name = "adminToPrincipalAssemblerImpl")
    private AdminToPrincipalAssembler adminToPrincipalAssembler;

    /**
     * 查找管理员
     *
     * @param username
     *         the username identifying the user whose data is required.
     *
     * @return {@link vip.dexian.admin.dto.AdminPrincipal}
     *
     * @throws UsernameNotFoundException
     */
    @Override
    @Transactional (readOnly = true, propagation = Propagation.SUPPORTS)
    public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException {

        if (StringUtils.isEmpty(username)) {
            throw new UsernameNotFoundException("username not found");
        }

        Admin admin = this.adminDao.findByUsername(username);

        if (admin == null) {
            throw new UsernameNotFoundException("username not found");
        }

        AdminPrincipal adminPrincipal = this.adminToPrincipalAssembler.toPrincipal(admin);

        List <AdminRoleDTO> adminRoleList = this.roleDao.findByAdmin(admin.getId());

        adminPrincipal.setRoles(adminRoleList);

        adminPrincipal.afterPropertiesSet();

        return adminPrincipal;
    }

    /**
     * 获取当前登录的管理员
     *
     * @return {@link AdminPrincipal}
     */
    public AdminPrincipal getCurrent () {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null) {
            return null;
        }

        if (authentication instanceof UsernamePasswordAuthenticationToken) {
            return (AdminPrincipal) authentication.getPrincipal();
        }

        return null;
    }

    /**
     * 登录成功
     *
     * @param ip
     *         登录ip
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void loginSuccess (String ip) {
        AdminPrincipal adminPrincipal = this.getCurrent();

        Admin admin = this.adminDao.find(adminPrincipal.getId());

        admin.setLoginIp(IpToLongUtils.toLong(ip));
        admin.setLoginDate(LocalDateTime.now());

        this.unlock(admin);
    }

    /**
     * 登录失败操作
     *
     * @param username
     *         用户名
     */
    @Override
    @Transactional (rollbackFor = Exception.class)
    public void loginFailure (String username) {

        if (StringUtils.isEmpty(username)) {
            log.debug("不进行账户失败相关操作，用户名为空");
            return;
        }

        // 登录失败锁定时间， 单位分钟。默认30分钟， 此处可以迁移至配置文件中
        int loginFailureLockTime = 30;
        // 失败锁定总次数
        int lockCount = 6;

        Admin admin = this.adminDao.findByUsername(username);

        if (admin == null) {
            log.error("不进行账户失败相关操作！管理员不存在，用户名：{}", username);
            return;
        }

        // 如果账户没有锁定，则更新登录失败次数
        if (Boolean.TRUE.equals(admin.getIsAccountNonLocked())) {

            int loginFailureCount = admin.getLoginFailureCount() + 1;

            if (loginFailureCount >= lockCount) {
                admin.setIsAccountNonLocked(false);
                admin.setLockedDate(LocalDateTime.now());
            }
            admin.setLoginFailureCount(loginFailureCount);
        } else {
            // 锁定的时间
            LocalDateTime lockedDate = admin.getLockedDate();

            LocalDateTime now = LocalDateTime.now();

            if (lockedDate == null) {
                lockedDate = now;
            }
            // 解锁的时间
            LocalDateTime unlockDate = lockedDate.plusMinutes(loginFailureLockTime);

            // 待锁定时间为30分钟以后，自动解锁
            if (LocalDateTimeUtils.isAfter(now, unlockDate)) {
                this.unlock(admin);
            }
        }
    }


    /**
     * 解锁
     *
     * @param admin
     *         管理员
     */
    private void unlock (Admin admin) {
        admin.setLoginFailureCount(0);
        admin.setIsAccountNonLocked(true);
        admin.setLockedDate(null);
    }

}
